﻿using System;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;

namespace MyUtils.UI
{
	public class DelegateCommand : ObservableObject, ICommand
	{
		readonly Action<object> _execute;
		readonly Func<object, bool> _canExecute;
		private readonly bool _requerySuggestion;

		public DelegateCommand(Action<object> execute, Func<object, bool> canExecute = null, bool requerySuggestion = true, TimeSpan requeryPool = default(TimeSpan))
		{
			if (execute == null)
			{
				throw new ArgumentNullException("execute");
			}
			_execute = execute;
			_canExecute = canExecute;
			_requerySuggestion = requerySuggestion;

			if (requeryPool > default(TimeSpan))
			{
				new Timer(PoolTick, null, requeryPool, requeryPool);
			}
		}

		void PoolTick(object state)
		{
			var application = Application.Current;
			if (application != null)
			{
				var dispatcher = application.Dispatcher;
				if (dispatcher != null)
				{
					dispatcher.BeginInvoke((Action)InvokeCanExecuteChanged);
				}
			}
		}

		#region Events

		private EventHandler _canExecuteChanged;
		public event EventHandler CanExecuteChanged
		{
			add
			{
				if (_canExecute != null)
				{
					if (_requerySuggestion)
					{
						CommandManager.RequerySuggested += value;
					}
					_canExecuteChanged += value;
				}
			}
			remove
			{
				if (_canExecute != null)
				{
					if (_requerySuggestion)
					{
						CommandManager.RequerySuggested -= value;
					}
					_canExecuteChanged -= value;
				}
			}
		}

		public void InvokeCanExecuteChanged()
		{
			var handler = _canExecuteChanged;
			if (handler != null)
			{
				handler(this, EventArgs.Empty);
			}
			OnPropertyChanged("CanExecuteState");
		}

		#endregion

		public void Execute(object parameter)
		{
			MsgBox.CatchToMsgbox(delegate
			{
				_execute(parameter);
			});
		}

		private bool? _lastCanExecute;

		private bool _quickAnswer;

		public bool CanExecuteState
		{
			get { return CanExecute(null); }
		}

		public bool CanExecute(object parameter)
		{
			if (_canExecute == null)
			{
				return true;
			}

			if (_lastCanExecute.HasValue && _quickAnswer)
			{
				_quickAnswer = false;
				return _lastCanExecute.Value;
			}

			var ev = new ManualResetEvent(false);

			ThreadPool.QueueUserWorkItem(x =>
			{
				var t = (Tuple<object, ManualResetEvent>)x;
				try
				{
					var oldCanExecute = _lastCanExecute;
					_lastCanExecute = _canExecute(t.Item1);
					t.Item2.Set();
					if (oldCanExecute != _lastCanExecute)
					{
						Application.Current.Dispatcher.BeginInvoke((Action)InvokeCanExecuteChanged, DispatcherPriority.ApplicationIdle);
					}
				}
				catch /*(Exception ex)*/
				{
					//XTrace.Exception(ex);
				}
			}, Tuple.Create(parameter, ev));
			if (!ev.WaitOne(TimeSpan.FromMilliseconds(333))) // если мы не справляемся быстро
			{
				// собщаем что пока команда недоступна, мы потом уведомим по результатом вычислений её доступности (BeginInvoke InvokeCanExecuteChanged)
				_quickAnswer = true;
				return false;
			}
			return _lastCanExecute.Value;

		}
	}
}
